/*
 * Copyright (c) 2007-2008 Sun Microsystems, Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to 
 * deal in the Software without restriction, including without limitation the 
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
 * sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 **/

package org.sunspotworld.util;

import com.sun.spot.peripheral.*;
import com.sun.spot.peripheral.radio.*;
import com.sun.spot.peripheral.ota.*;
import com.sun.spot.peripheral.radio.mhrp.lqrp.Constants;

import com.sun.spot.io.j2me.radio.*;
import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.util.Utils;
import com.sun.spot.util.Queue;

import java.io.*;


/**
 *
 * Auxiliary class to print out information about received radio packets
 *
 *
 * Packet format:
 *
 * MAC layer: Frame control byte : seq : dest : src - all unpacked into RadioPacket fields
 *
 * Lowpan layer: [ fragmentation | protocol | meshinfo ] 
 *               [ fragmentation header ]
 *               [ mesh header ]
 *                 data payload
 *
 * @author Ron Goldman
 */
public class PacketPrinter implements Runnable {
    
    private static final int OLD_RADIOSTREAM_PROTOCOL_NUMBER = 100;
    private static final int OLD_RADIOGRAM_PROTOCOL_NUMBER = 101;
    private static final int RADIOSTREAM_PROTOCOL_NUMBER = 104;
    private static final int RADIOGRAM_PROTOCOL_NUMBER = 105;

    private static IProprietaryRadio radio = Spot.getInstance().getIProprietaryRadio();
    private Queue printQueue = new Queue();
    private Queue freeQueue = new Queue();
    private final StringBuffer buffer = new StringBuffer(2048);
    private byte pktBuffer[] = new byte[128];


    /** Creates a new instance of PacketPrinter */
    public PacketPrinter(Queue printQueue, Queue freeQueue) {
        this.printQueue = printQueue;
        this.freeQueue = freeQueue;
    }
    
    public void run() {
        while (true) {
            Object pkt = printQueue.get();
            LowPanPacket lpp = null;
            RadioPacket rp = null;
            if (pkt instanceof LowPanPacket) {
                lpp = (LowPanPacket)pkt;
                rp = lpp.getRadioPacket();
            } else {
                rp = (RadioPacket)pkt;
            }
            countPacket(rp, lpp);
            printPacket(rp, lpp);
            freeQueue.put(rp);
            Utils.sleep(100);   // need some delay or will overflow print buffer
        }
    }
 
    
    private String toHexString(long num) {
        String bot = Integer.toHexString((int)(num & 0xFFFFFFFF));
        if (bot.length() < 8) {
            bot = "00000000".substring(bot.length()) + bot;
        }
        return Integer.toHexString((int)(num >> 32)) + bot;
    }
    
    private void printPacket(RadioPacket rp, LowPanPacket lpp) {
        StringBuffer result = buffer; 
        result.setLength(0); 
        result.append("RP:");
        // uncomment to display MAC frame control bits
        // result.append(" fcntl: "); 
        // result.append(Integer.toHexString(rp.getFrameControl()));
        result.append(" rssi: ");
        result.append(rp.getRssi());
        if (rp.isAck()) {
            result.append(" ack  seq: ");
            result.append(((int)rp.getDataSequenceNumber() & 0xff));
            result.append("\n");
        } else {
            boolean printPayload = true;
            boolean printAODVinfo = false;
            byte AODVMessageType = 0;
            if (rp.isData()) result.append(" dat  ");
            boolean fragged = lpp.isFragged();
            boolean mesh = lpp.isMeshed();
            boolean protoPkt = true;
            if (fragged) {
                if (lpp.isFirstFrag()) {
                    result.append("first fragment ");
                } else {
                    result.append("interior fragment ");
                    protoPkt = false;
                }
            }
            int dataStart = lpp.getHeaderLength();
            int dataLength = lpp.getPayloadSize();
            if (protoPkt) {
                int proto = lpp.getProtocol();
                int data = (int)(0xFF & rp.getMACPayloadAt(dataStart));                
                if (proto == RADIOGRAM_PROTOCOL_NUMBER) {
                    result.append("radiogram  port: ");
                    result.append(data);
                    dataStart++;
                    dataLength--;
                } else if (proto == RADIOSTREAM_PROTOCOL_NUMBER) {
                    result.append("radiostream  port: ");
                    result.append(data);
                    dataStart++;
                    dataLength--;
                } else if (proto == Constants.LQRP_PROTOCOL_NUMBER) {
                    result.append("aodv  ");
                    AODVMessageType = (byte)data;
                    printAODVinfo = true;
                    printPayload = false;
                    switch (AODVMessageType) {
                        case Constants.RREQ_TYPE:
                            result.append("RREQ  ");
                            break;
                        case Constants.RREP_TYPE:
                            result.append("RREP  ");
                            break;
                        case Constants.RERR_TYPE:
                            result.append("RERR  ");
                            break;
                        default:
                            result.append("Unknown: ");
                            result.append(data);
                            printAODVinfo = false;
                            printPayload = true;
                            break;
                    }
                } else if (proto == OLD_RADIOSTREAM_PROTOCOL_NUMBER) {
                    result.append("old radiostream  port: ");
                    result.append(data);
                    dataStart++;
                    dataLength--;
                } else if (proto == OLD_RADIOGRAM_PROTOCOL_NUMBER) {
                    result.append("old radiogram  port: ");
                    result.append(data);
                    dataStart++;
                    dataLength--;
                } else {
                    result.append("protocol: ");
                    result.append(proto);
                }
            }
            result.append(" seq: ");
            result.append(((int)rp.getDataSequenceNumber() & 0xff));
            result.append(" (");
            result.append(rp.getMACPayloadLength());
            result.append(" bytes)\n    from ");
            result.append(rp.getSourcePanID());
            result.append(" : ");
            result.append(toHexString(rp.getSourceAddress()));
            result.append(" to ");
            result.append(rp.getDestinationPanID());
            result.append(" : ");
            long addr = rp.getDestinationAddress();
            if (lpp.isBCast()) {
                result.append("lowpan broadcast: seq #" + lpp.getBCastSeqNo());
            } else if (addr == 0xFFFF) {
                result.append("broadcast");
            } else {
                result.append(toHexString(addr));
            }
            if (mesh) {
                long destination = lpp.getFDestinationAddress();
                if (addr != destination) {
                    result.append("   forwarding to: ");
                    result.append(toHexString(destination));
                } else {
                    result.append("   from: ");
                    result.append(toHexString(lpp.getOriginatorAddress()));
                }
                result.append("  hops: ");
                result.append(lpp.getHopsLeft());
            }
            if (printPayload) {
                result.append('\n');
                for (int i = 0; i < dataLength; i++) {
                    pktBuffer[i] = rp.getMACPayloadAt(dataStart + i);
                }
                PrettyPrint.prettyPrint(result, pktBuffer, 0, dataLength);
            } else if (printAODVinfo) {
                int hops = (int)(0xFF & rp.getMACPayloadAt(dataStart + 3));
                switch (AODVMessageType) {
                    case Constants.RREQ_TYPE:
                        result.append("\n    hops: ");
                        result.append(hops);
                        result.append("   seeking route to: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 8)));
                        result.append("   from: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 20)));
                        break;
                    case Constants.RREP_TYPE:
                        result.append("\n    hops: ");
                        result.append(hops);
                        result.append("   dest: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 4)));
                        result.append("   origin: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 16)));
                        break;
                    case Constants.RERR_TYPE:
                        result.append("\n    cnt: ");
                        result.append(hops);
                        result.append("   dest: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 4)));
                        result.append("   origin: ");
                        result.append(toHexString(rp.getMACPayloadBigEndLongAt(dataStart + 12)));
                        break;
                    default:
                        result.append("Unknown: ");
                        break;
                }
                result.append("\n");
            } else {
                result.append("\n");
            }
        }
        System.out.print(result.toString());
    }

    private int packetCnt = 0;
    private int ackCnt = 0;
    private int radiogramCnt = 0;
    private int radiostreamCnt = 0;
    private int aodvCnt = 0;
    private int unkCnt = 0;
    private int fragCnt = 0;

    private void printCounts() {
        System.out.println("Packets received: " + packetCnt + "   acks: " + ackCnt + 
                           "   other: " + (radiogramCnt + radiostreamCnt + aodvCnt + fragCnt + unkCnt));
        System.out.println("   radiograms: " + radiogramCnt + "   radiostream: " + radiostreamCnt + 
                           "   aodv: " + aodvCnt + "   fragments: " + fragCnt + "   unknown: " + unkCnt);
        int o = radio.getRxOverflow();
        if (o > 0) {
            System.out.println("--- radio buffer overflows: " + o);
        }
        o = radio.getCrcError();
        if (o > 0) {
            System.out.println("--- corrupted packets: " + o);
        }
    }
    
    private void countPacket(RadioPacket rp, LowPanPacket lpp) {
        packetCnt++;
        if (rp.isAck()) {
            ackCnt++;
        } else {
            boolean fragged = lpp.isFragged();
            if (fragged) {
                fragCnt++;
            }
            if (!fragged || lpp.isFirstFrag()) {   // unfragmented or first fragment
                int proto = lpp.getProtocol();
                int data = (int)(0xFF & rp.getMACPayloadAt(lpp.getLppPayloadOffset()));
                if (proto == RADIOGRAM_PROTOCOL_NUMBER) {
                    radiogramCnt++;
                } else if (proto == RADIOSTREAM_PROTOCOL_NUMBER) {
                    radiostreamCnt++;
                } else if (proto == Constants.LQRP_PROTOCOL_NUMBER) {
                    aodvCnt++;
                    switch (data) {
                        case Constants.RREQ_TYPE:
                            break;
                        case Constants.RREP_TYPE:
                            break;
                        case Constants.RERR_TYPE:
                            break;
                        default:
                            break;
                    }
                } else if (proto == OLD_RADIOSTREAM_PROTOCOL_NUMBER) {
                    radiostreamCnt++;
                } else if (proto == OLD_RADIOGRAM_PROTOCOL_NUMBER) {
                    radiogramCnt++;
                } else {
                    unkCnt++;
                }
            }
        }
        if (packetCnt % 100 == 0) printCounts();
    }

}
